package org.osami.commons.eventbuffer;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.Vector;

//import org.osami.syncagent.SyncAgentService;
import org.osgi.framework.BundleContext;
//import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;

public class EventBuffer implements EventHandler {
	private BundleContext bc;
	private Hashtable<String,Vector<String>> eventBuffers = new Hashtable<String,Vector<String>>();
	private Vector<String> absentClients = new Vector<String>();
	
	//TODO: if syncAgent is removed, remove context as parameter
	public EventBuffer(BundleContext context) {
		// TODO Auto-generated constructor stub
		bc = context;
	}
 
	public void addEvent(String event, String deviceId) {
		// TODO Auto-generated method stub
		// check if a key already exists for this device; if not, create a new list and send a push message. 
		// If yes, add the event to the list.
		System.out.println("EventBuffer: addEvent for device " +deviceId);
		if (deviceId != null) {
			Vector<String> events = eventBuffers.get(deviceId);
			
			if (events == null) {
				events = new Vector<String>();
				eventBuffers.put(deviceId, events);
				System.out.println("EventBuffer: Add Event "+deviceId+" "+event);
				//Notify the absent client that messages are being queued
				notifyAbsentClient(deviceId);
			}
						
			events.add(event);
			System.out.println("Events in queue for device "+deviceId+": "+events.size());
		}
	}

	private void notifyAbsentClient(String deviceId) {
		// TODO Auto-generated method stub
		// For (android) phones, send a push message. TODO at some point: determine the notification method
		// by e.g. device type (needs to be made available first)
		System.out.println("EventBuffer: Now we would notify the absent client ("+deviceId+")");
	}

	public void registerDevice(String deviceId,URI deviceAddress) {
		// TODO Auto-generated method stub
		// an absent device has become available again; if messages have accumulated for the device, 
		// send them via the sync agent
		System.out.println("EventBuffer: registerDevice "+deviceId+" "+deviceAddress);
		if (deviceId != null) {
			Vector<String> messages = eventBuffers.get(deviceId);
			System.out.println("EventBuffer: messages="+messages);
			if(messages != null) {
				//get syncAgent reference
				System.out.println("EventBuffer: messages.size="+messages.size());
				// TODO: need to worry about sync agent disappearing while sending the list?				
				
				//Send directly to the specified URI
				// TODO: should SyncAgent be used instead? There is no method in SA currently for that,
				// might be against the SyncAgent philosophy to send to a specified URI directly?
				
				Socket clientSocket = null;
				PrintWriter out = null;
				try {
					clientSocket = new Socket(deviceAddress.getHost(),deviceAddress.getPort());
					out = new PrintWriter(clientSocket.getOutputStream(), true);					
				} catch (UnknownHostException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				for (String message:messages) {
					System.out.println("EventBuffer: sending message: "+message+" to host:"+deviceAddress.getHost()+
							", port:"+deviceAddress.getPort());
					out.println(message);					
				}
				
				out.close();
				
				try {
					clientSocket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
				
				eventBuffers.remove(deviceId);
			}
		}
	}
	
	//TODO - this method is almost the same as eventToString in SyncAgentServer. It would be better to keep the event 
	// as an osgi Event until actually needed to turn into a string (both here and in SyncAgent)
	// OR create a new, common Event class that extends the OSGi event by overriding toString() 
	// into something meaningful
	private String eventToStringMsg(Event event) {
		String message;				
		message = "event;topic="+event.getTopic()+",";
		String[] params = event.getPropertyNames();
		for(int i = 0; i < params.length; i++)
		{
			if (i == params.length-1)
			{
				message += params[i]+"="+event.getProperty(params[i]);
			}
			else 
			{
				message += params[i]+"="+event.getProperty(params[i])+",";  
			}
		}
		return message;
	}

	@Override
	public void handleEvent(Event event) {
		// TODO Auto-generated method stub
		String[] eventTopicSections = event.getTopic().split("/");
		String topic = eventTopicSections[eventTopicSections.length-1];
		String deviceId = (String)event.getProperty("deviceId");
		String URI = (String)event.getProperty("URI");
		
		if(topic.equals("Register")) {
			//if this client is in the absent clients list, notify the Event Buffer bundle and remove the client from the list				
			System.out.println("EventBuffer: deviceId: "+deviceId);
			if(deviceId != "") {
				int index = absentClients.indexOf(deviceId);
				if(index >-1) {
					absentClients.remove(index);
					System.out.println("EventBuffer: absent clients left: "+absentClients.size());					
					try {
						registerDevice(deviceId, new URI(URI));						
					} catch (URISyntaxException e) {
						// TODO Auto-generated catch block
						System.out.println("Malformed URI: "+URI);
					}					
				}else {
					System.out.println("EventBuffer: client "+deviceId+" not absent, registration not sent");
				}
			}	
			return;
		} 
		
		if (topic.equals("Deregister")) {
			//If a deviceID is provided, add this client to the absent clients list 
			if(deviceId != "") {
				if(absentClients.indexOf(deviceId)==-1) { //Shouldn't be on the list but no harm to check
					absentClients.add(deviceId);
					System.out.println("EventBuffer: absentClient added: "+deviceId);
					System.out.println("EventBuffer: absentClients.size(): "+absentClients.size());							
				} else {
					System.out.println("deviceID "+deviceId+" unexpectedly already in absent client list!");
				}
			}
			return;	
		}

		//All other registered messages are buffered.
		//buffer the message for all absent clients
		if(absentClients.size() > 0) {
			for (String tempDeviceId:absentClients) {
				addEvent(eventToStringMsg(event), tempDeviceId);	
			}					
		}
	}
}
